/*
 * Copyright (c) Thorben Linneweber and others
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using SoftFloat;

namespace Jitter2.LinearMath
{
	
	/// <summary>
	/// Represents a three-dimensional vector using three sfloating-point numbers.
	/// </summary>
	[Serializable]
	[StructLayout(LayoutKind.Sequential, Size = 12)]
	public partial struct NVector3
	{
	    internal static NVector3 InternalZero;
	    internal static NVector3 Arbitrary;
	
	    public sfloat X;
	    public sfloat Y;
	    public sfloat Z;
	
	    public static readonly NVector3 Zero;
	    public static readonly NVector3 UnitX;
	    public static readonly NVector3 UnitY;
	    public static readonly NVector3 UnitZ;
	    public static readonly NVector3 One;
	    public static readonly NVector3 MinValue;
	    public static readonly NVector3 MaxValue;
	
	    static NVector3()
	    {
	        One = new NVector3(1, 1, 1);
	        Zero = new NVector3(0, 0, 0);
	        UnitX = new NVector3(1, 0, 0);
	        UnitY = new NVector3(0, 1, 0);
	        UnitZ = new NVector3(0, 0, 1);
	        MinValue = new NVector3(sfloat.MinValue);
	        MaxValue = new NVector3(sfloat.MaxValue);
	        Arbitrary = new NVector3(1, 1, 1);
	        InternalZero = Zero;
	    }
	
	    public NVector3(sfloat x, sfloat y, sfloat z)
	    {
	        X = x;
	        Y = y;
	        Z = z;
	    }
	
	    public void Set(sfloat x, sfloat y, sfloat z)
	    {
	        X = x;
	        Y = y;
	        Z = z;
	    }
	
	    public NVector3(sfloat xyz)
	    {
	        X = xyz;
	        Y = xyz;
	        Z = xyz;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public unsafe ref sfloat UnsafeGet(int index)
	    {
	        sfloat* ptr = (sfloat*)Unsafe.AsPointer(ref this);
	        return ref ptr[index];
	    }
	
	    public unsafe sfloat this[int i]
	    {
	        get
	        {
	            fixed (sfloat* ptr = &X)
	            {
	                return ptr[i];
	            }
	        }
	        set
	        {
	            fixed (sfloat* ptr = &X)
	            {
	                ptr[i] = value;
	            }
	        }
	    }
	
	    public readonly override string ToString()
	    {
	        return $"{X.ToString("F6")} {Y.ToString("F6")} {Z.ToString("F6")}";
	    }
	
	    public readonly override bool Equals(object? obj)
	    {
	        if (obj is not NVector3) return false;
	        NVector3 other = (NVector3)obj;
	
	        return X == other.X && Y == other.Y && Z == other.Z;
	    }
	
	    public static bool operator ==(NVector3 value1, NVector3 value2)
	    {
	        return value1.X == value2.X && value1.Y == value2.Y && value1.Z == value2.Z;
	    }
	
	    public static bool operator !=(NVector3 value1, NVector3 value2)
	    {
	        if (value1.X == value2.X && value1.Y == value2.Y)
	        {
	            return value1.Z != value2.Z;
	        }
	
	        return true;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Min(in NVector3 value1, in NVector3 value2)
	    {
	        Min(value1, value2, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Min(in NVector3 value1, in NVector3 value2, out NVector3 result)
	    {
	        result.X = value1.X < value2.X ? value1.X : value2.X;
	        result.Y = value1.Y < value2.Y ? value1.Y : value2.Y;
	        result.Z = value1.Z < value2.Z ? value1.Z : value2.Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Max(in NVector3 value1, in NVector3 value2)
	    {
	        Max(value1, value2, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Abs(in NVector3 value1)
	    {
	        return new NVector3(libm.Abs(value1.X), libm.Abs(value1.Y), libm.Abs(value1.Z));
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static sfloat MaxAbs(in NVector3 value1)
	    {
	        NVector3 abs = Abs(value1);
	        return libm.Max(libm.Max(abs.X, abs.Y), abs.Z);
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Max(in NVector3 value1, in NVector3 value2, out NVector3 result)
	    {
	        result.X = value1.X > value2.X ? value1.X : value2.X;
	        result.Y = value1.Y > value2.Y ? value1.Y : value2.Y;
	        result.Z = value1.Z > value2.Z ? value1.Z : value2.Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public void MakeZero()
	    {
	        X = (sfloat)0.0f;
	        Y = (sfloat)0.0f;
	        Z = (sfloat)0.0f;
	    }
	
	    /// <summary>
	    /// Calculates matrix \times vector, where vector is a column vector.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Transform(in NVector3 vector, in JMatrix matrix)
	    {
	        Transform(vector, matrix, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Transform(in NVector3 vector, in NQuaternion quat)
	    {
	        Transform(vector, quat, out NVector3 result);
	        return result;
	    }
	
	    /// <summary>
	    /// Calculates matrix^\mathrf{T} \times vector, where vector is a column vector.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 TransposedTransform(in NVector3 vector, in JMatrix matrix)
	    {
	        TransposedTransform(vector, matrix, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 TransposedTransform(in NVector3 vector, in NQuaternion quat)
	    {
	        ConjugatedTransform(vector, quat, out NVector3 result);
	        return result;
	    }
	
	    /// <summary>
	    /// Calculates matrix \times vector, where vector is a column vector.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Transform(in NVector3 vector, in JMatrix matrix, out NVector3 result)
	    {
	        sfloat num0 = vector.X * matrix.M11 + vector.Y * matrix.M12 + vector.Z * matrix.M13;
	        sfloat num1 = vector.X * matrix.M21 + vector.Y * matrix.M22 + vector.Z * matrix.M23;
	        sfloat num2 = vector.X * matrix.M31 + vector.Y * matrix.M32 + vector.Z * matrix.M33;
	
	        result.X = num0;
	        result.Y = num1;
	        result.Z = num2;
	    }
	
	    /// <summary>
	    /// Calculates matrix^\mathrf{T} \times vector, where vector is a column vector.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void TransposedTransform(in NVector3 vector, in JMatrix matrix, out NVector3 result)
	    {
	        sfloat num0 = vector.X * matrix.M11 + vector.Y * matrix.M21 + vector.Z * matrix.M31;
	        sfloat num1 = vector.X * matrix.M12 + vector.Y * matrix.M22 + vector.Z * matrix.M32;
	        sfloat num2 = vector.X * matrix.M13 + vector.Y * matrix.M23 + vector.Z * matrix.M33;
	
	        result.X = num0;
	        result.Y = num1;
	        result.Z = num2;
	    }
	
	    /// <summary>
	    /// Transforms the vector by a quaternion.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Transform(in NVector3 vector, in NQuaternion quaternion, out NVector3 result)
	    {
	        sfloat numx = (sfloat)2.0f * (quaternion.Y * vector.Z - quaternion.Z * vector.Y);
	        sfloat numy = (sfloat)2.0f * (quaternion.Z * vector.X - quaternion.X * vector.Z);
	        sfloat numz = (sfloat)2.0f * (quaternion.X * vector.Y - quaternion.Y * vector.X);
	
	        sfloat num00 = quaternion.Y * numz - quaternion.Z * numy;
	        sfloat num11 = quaternion.Z * numx - quaternion.X * numz;
	        sfloat num22 = quaternion.X * numy - quaternion.Y * numx;
	
	        result.X = vector.X + quaternion.W * numx + num00;
	        result.Y = vector.Y + quaternion.W * numy + num11;
	        result.Z = vector.Z + quaternion.W * numz + num22;
	    }
	
	    /// <summary>
	    /// Transforms the vector by a conjugated quaternion.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void ConjugatedTransform(in NVector3 vector, in NQuaternion quaternion, out NVector3 result)
	    {
	        sfloat numx = (sfloat)2.0f * (quaternion.Z * vector.Y - quaternion.Y * vector.Z);
	        sfloat numy = (sfloat)2.0f * (quaternion.X * vector.Z - quaternion.Z * vector.X);
	        sfloat numz = (sfloat)2.0f * (quaternion.Y * vector.X - quaternion.X * vector.Y);
	
	        sfloat num00 = quaternion.Z * numy - quaternion.Y * numz;
	        sfloat num11 = quaternion.X * numz - quaternion.Z * numx;
	        sfloat num22 = quaternion.Y * numx - quaternion.X * numy;
	
	        result.X = vector.X + quaternion.W * numx + num00;
	        result.Y = vector.Y + quaternion.W * numy + num11;
	        result.Z = vector.Z + quaternion.W * numz + num22;
	    }
	
	    /// <summary>
	    /// Calculates the outer product.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static JMatrix Outer(in NVector3 u, in NVector3 v)
	    {
	        JMatrix result;
	        result.M11 = u.X * v.X;
	        result.M12 = u.X * v.Y;
	        result.M13 = u.X * v.Z;
	        result.M21 = u.Y * v.X;
	        result.M22 = u.Y * v.Y;
	        result.M23 = u.Y * v.Z;
	        result.M31 = u.Z * v.X;
	        result.M32 = u.Z * v.Y;
	        result.M33 = u.Z * v.Z;
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static sfloat Dot(in NVector3 vector1, in NVector3 vector2)
	    {
	        return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Add(in NVector3 value1, in NVector3 value2)
	    {
	        Add(value1, value2, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Add(in NVector3 value1, in NVector3 value2, out NVector3 result)
	    {
	        result.X = value1.X + value2.X;
	        result.Y = value1.Y + value2.Y;
	        result.Z = value1.Z + value2.Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Subtract(NVector3 value1, NVector3 value2)
	    {
	        Subtract(value1, value2, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Subtract(in NVector3 value1, in NVector3 value2, out NVector3 result)
	    {
	        sfloat num0 = value1.X - value2.X;
	        sfloat num1 = value1.Y - value2.Y;
	        sfloat num2 = value1.Z - value2.Z;
	
	        result.X = num0;
	        result.Y = num1;
	        result.Z = num2;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Cross(in NVector3 vector1, in NVector3 vector2)
	    {
	        Cross(vector1, vector2, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Cross(in NVector3 vector1, in NVector3 vector2, out NVector3 result)
	    {
	        sfloat num0 = vector1.Y * vector2.Z - vector1.Z * vector2.Y;
	        sfloat num1 = vector1.Z * vector2.X - vector1.X * vector2.Z;
	        sfloat num2 = vector1.X * vector2.Y - vector1.Y * vector2.X;
	
	        result.X = num0;
	        result.Y = num1;
	        result.Z = num2;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public readonly override int GetHashCode()
	    {
	        return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode();
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public void Negate()
	    {
	        X = -X;
	        Y = -Y;
	        Z = -Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Negate(in NVector3 value)
	    {
	        Negate(value, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Negate(in NVector3 value, out NVector3 result)
	    {
	        sfloat num0 = -value.X;
	        sfloat num1 = -value.Y;
	        sfloat num2 = -value.Z;
	
	        result.X = num0;
	        result.Y = num1;
	        result.Z = num2;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Normalize(in NVector3 value)
	    {
	        Normalize(value, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public void Normalize()
	    {
	        sfloat num2 = X * X + Y * Y + Z * Z;
	        sfloat num = 1f / (sfloat)libm.Sqrt(num2);
	        X *= num;
	        Y *= num;
	        Z *= num;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Normalize(in NVector3 value, out NVector3 result)
	    {
	        sfloat num2 = value.X * value.X + value.Y * value.Y + value.Z * value.Z;
	        sfloat num = 1f / (sfloat)libm.Sqrt(num2);
	        result.X = value.X * num;
	        result.Y = value.Y * num;
	        result.Z = value.Z * num;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public readonly sfloat LengthSquared()
	    {
	        return X * X + Y * Y + Z * Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public readonly sfloat Length()
	    {
	        return libm.Sqrt(X * X + Y * Y + Z * Z);
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Swap(ref NVector3 vector1, ref NVector3 vector2)
	    {
	        (vector2, vector1) = (vector1, vector2);
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 Multiply(in NVector3 value1, sfloat scaleFactor)
	    {
	        Multiply(value1, scaleFactor, out NVector3 result);
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public void Multiply(sfloat factor)
	    {
	        X *= factor;
	        Y *= factor;
	        Z *= factor;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static void Multiply(in NVector3 value1, sfloat scaleFactor, out NVector3 result)
	    {
	        result.X = value1.X * scaleFactor;
	        result.Y = value1.Y * scaleFactor;
	        result.Z = value1.Z * scaleFactor;
	    }
	
	    /// <summary>
	    /// Calculates the cross product.
	    /// </summary>
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator %(in NVector3 vector1, in NVector3 vector2)
	    {
	        NVector3 result;
	        result.X = vector1.Y * vector2.Z - vector1.Z * vector2.Y;
	        result.Y = vector1.Z * vector2.X - vector1.X * vector2.Z;
	        result.Z = vector1.X * vector2.Y - vector1.Y * vector2.X;
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static sfloat operator *(in NVector3 vector1, in NVector3 vector2)
	    {
	        return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator *(in NVector3 value1, sfloat value2)
	    {
	        NVector3 result;
	        result.X = value1.X * value2;
	        result.Y = value1.Y * value2;
	        result.Z = value1.Z * value2;
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator *(sfloat value1, in NVector3 value2)
	    {
	        NVector3 result;
	        result.X = value2.X * value1;
	        result.Y = value2.Y * value1;
	        result.Z = value2.Z * value1;
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator -(in NVector3 value1, in NVector3 value2)
	    {
	        NVector3 result;
	        result.X = value1.X - value2.X;
	        result.Y = value1.Y - value2.Y;
	        result.Z = value1.Z - value2.Z;
	        return result;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator -(in NVector3 left)
	    {
	        return Multiply(left, -(sfloat)1.0f);
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator +(in NVector3 left)
	    {
	        return left;
	    }
	
	    [MethodImpl(MethodImplOptions.AggressiveInlining)]
	    public static NVector3 operator +(in NVector3 value1, in NVector3 value2)
	    {
	        NVector3 result;
	        result.X = value1.X + value2.X;
	        result.Y = value1.Y + value2.Y;
	        result.Z = value1.Z + value2.Z;
	
	        return result;
	    }
	}
}
